#include <linux/module.h>		// module_init  module_exit  函数头文件
#include <linux/init.h>			// __init   __exit   宏头文件     路径： kernel/include/linux/init.h
#include <linux/fs.h>           // register_chrdev 
// 模块安装函数，static  chrdev_init 不被内核里面其他的.c 调用，只能被宏调用  


#include <asm/uaccess.h>   // copy_to_user  ,  copy_from_user  函数
// #include <asm-generic/uaccess.h>    
#include <linux/cdev.h>   //cdev 

#include <linux/device.h>   // class  操作函数



#include <mach/regs-gpio.h>   // gpio-bank 中使用了 regs-gpio.h 的宏，静态映射地址都在这里
#include <mach/gpio-bank.h>   // gpio-bank.h  GPJ0CON_D  寄存器头文件
#include <asm/string.h>     // memset头文件 


#include <linux/ioport.h>  // 动态内存映射头文件   request_mem_region 
#include <asm/io.h>

#include <linux/string.h>     //内核中的strcmp 函数

#define majorName "testchar"

int mymajor;   //定义全局变量，注销的时候要用的


// 裸机物理地址，动态映射  物理地址 手册上的
#define GPJ0CON_PA	0xe0200240
#define GPJ0DAT_PA 	0xe0200244
unsigned int *pGPJ0CON;
unsigned int *pGPJ0DAT;




#define MY_MAJOR_ID 200
#define MY_COUNT 1
char kbuf[100]={0};			// 内核空间的buf
static struct cdev* p_cdev;  // 使用指针替换上面全局变量，有点，这里是堆内存结构体在，上面全局变量无法自己释放

static dev_t mydev; 
struct class *test_class;

static int test_chrdev_release(struct inode *inode, struct file *file)
{
	printk(KERN_INFO "test_chrdev_release\n");
	
	return 0;
}

static int test_chrdev_open(struct inode *inode, struct file *file)
{
	// 这个函数中真正应该放置的是打开这个设备的硬件操作代码部分
	// 但是现在暂时我们写不了这么多，所以用一个printk打印个信息来做代表。
	printk(KERN_INFO "test_chrdev_open\n");
   
	
	return 0;
}

// 写函数的本质就是将应用层传递过来的数据先复制到内核中，然后将之以正确的方式写入硬件完成操作。
static ssize_t test_chrdev_write(struct file *file, const char __user *ubuf,
	size_t count, loff_t *ppos)
{
		int ret = -1;
	printk(KERN_INFO "test_chrdev_write\n");	
	
	//  应用空间的ubuf ， kbuf 内核空间的buf ,  从 用户空间ubuf 复制到内核空间 kbuf 
	//memcpy(kbuf, ubuf);		// 不行，因为2个不在一个地址空间中

	ret = copy_from_user(kbuf, ubuf, count);
	if (ret)
	{
		printk(KERN_ERR "copy_from_user fail\n");
		return -EINVAL;
	}

	return 0;
}


ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos)
{
	
	int ret = -1;
	printk(KERN_INFO "test_chrdev_read\n");
	
    //  从内核空间空间 复制到用户空间 ， 从kbuf 到 ubuf
    ret = copy_to_user(ubuf, kbuf, count);
	if (ret)
	{
		printk(KERN_ERR "copy_to_user fail\n");
		return -EINVAL;
	}


	return 0;
}

// 自定义一个file_operations结构体变量，并且去填充
static const struct file_operations test_fops = {
	.owner		= THIS_MODULE,				// 惯例，直接写即可
	
	.open		= test_chrdev_open,			// 将来应用open打开这个设备时实际调用的
	.release	= test_chrdev_release,		// 就是这个.close对应的函数
	.write 		= test_chrdev_write,
	.read		= test_chrdev_read,
};


static int __init chrdev_init(void)
{	



    int retval; 
	printk(KERN_INFO "chrdev_init helloworld init\n");
#if 0
    //1.1 写死主设备号 ，静态申请
     mydev  = MKDEV(MY_MAJOR_ID, 0);   // 参数1， 主设备号  0 是次设备号开始，  1 
     retval = register_chrdev_region(mydev,MY_COUNT, majorName);  //  MY_COUNT：  1 次设备号个数， 2就是 0 ，
     if(retval){
        printk(KERN_INFO "register_chrdev_region , majorName=%s   \n", majorName); 
        return -EINVAL; 
	   //  goto flag1; 
     }
     printk(KERN_INFO "register_chrdev_region suc , MY_MAJOR_ID=%d   \n", MY_MAJOR_ID);


  // majorName 的功能，  比如cat /proc/devices  可以根据上面的 majorName 来查找对应的设备号
   // mknod  /dev/driver_test  c 200 0
#endif

   
    // 动态申请，申请号以后主设备号保存到 dev_t 中
    //1.2 自动分配主设备号   次设备号从12开始，2个
   retval = alloc_chrdev_region(&mydev, 12, MY_COUNT, majorName);
   if (retval) {
    printk(KERN_ERR "cdev_add fail ");
    //return -EINVAL;
    goto flag1; 
   }   
   printk(KERN_INFO "alloc_chrdev_region suc");
   // 申请成功以后，通过这2个宏，获取主设备号，次设备号
   printk(KERN_INFO "major= %d ,minor= %d ",MAJOR(mydev),MINOR(mydev)); 



     //2. 初始化结构体， cdev 绑定 file_operations 结构体 
      p_cdev = cdev_alloc();
	  cdev_init(p_cdev, &test_fops);

	  //3.  file_operations结构体、  驱动添加到内核  
      retval = cdev_add(p_cdev, mydev, MY_COUNT);  // cdev、设备号、数量  
      if (retval) {
         printk(KERN_ERR "cdev_add fail ");
        //  return -EINVAL;
            goto flag2;   
      }   
      printk(KERN_INFO "cdev_add  suc\n");

  

      // 使用代码实现 mknod 功能   aston_class 是类名 , 让内核发送信息 给udev, 让udev自动创建，删除设备文件
      test_class = class_create(THIS_MODULE, "aston_class");
      if (IS_ERR(test_class)) {
          
        return -EINVAL; 
      }

      // 最后一个参数设备文件名， 将来在/dev 目录下和 sys/class/类名/*  目录下创建的设备名 
       // /dev/driver_test
      // /sys/class/aston_class/driver_test
      // aston_class 可以理解为led
      /// driver_test 可以理解为led1   led2 设备 
      device_create(test_class, NULL, mydev, NULL, "driver_test");



    //动态映射功能，   向内核申请要进行动态映射,和malloc一样，要内存资源需要申请释放
    // 功能： 就是看一下该寄存器是否已经被申请了,如果已经申请了,那么失败
    // 参数1：寄存器， 映射长度， 寄存器名字
    if(!request_mem_region(GPJ0CON_PA, 4,"pGPJ0CON_D")){
      return -1;
    }

    if(!request_mem_region(GPJ0DAT_PA, 4,"pGPJ0DAT_D")){
      return -1;
    }

    pGPJ0CON = ioremap(GPJ0CON_PA, 4);   //返回虚拟地址
	  pGPJ0DAT = ioremap(GPJ0DAT_PA, 4);

    printk(KERN_INFO "pGPJ0CON = %#x  pGPJ0DAT= %#x \n",pGPJ0CON,pGPJ0DAT);

	  *pGPJ0CON = 0x11111111;
	  *pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));		// 亮




   goto flag0;

// 内核倒影式出错处理机制
flag4:
     
     
flag3:
    //  1. 注销字符设备驱动 cdev  上面add了，这个要del ;
        cdev_del(p_cdev);  //释放p_cdev的堆内存
flag2: 
// 把 第1，2 步的东西释放调 
// 内核倒影式出错处理机制    执行flag2 以后继续玩下执行 flag1,知道遇到return 
      unregister_chrdev_region(mydev,MY_COUNT);
flag1: 
      return -EINVAL; 
flag0: 
	  return 0;
}

// 模块卸载函数
static void __exit chrdev_exit(void)
{
	printk(KERN_INFO "chrdev_exit helloworld exit1\n");

   // 关灯 、 取消映射、 释放资源
  *pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
    	// 解除映射
	iounmap(pGPJ0CON);
	iounmap(pGPJ0DAT);
	release_mem_region(GPJ0CON_PA, 4);
	release_mem_region(GPJ0DAT_PA, 4);

    device_destroy(test_class,mydev);
    class_destroy(test_class);


	// 释放p_cdev的堆内存
    cdev_del(p_cdev);   
    //  2. 上面注册主次设备号， 这里unregister_
    unregister_chrdev_region(mydev,MY_COUNT);
}


module_init(chrdev_init);
module_exit(chrdev_exit);

// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");				// linux 内核是开源的，你自己的模块也要GPL, 否则调用内核出现莫名奇妙错误有可能
MODULE_AUTHOR("aston");				// 描述模块的作者
MODULE_DESCRIPTION("module test");	// 描述模块的介绍信息
MODULE_ALIAS("alias xxx");			// 描述模块的别名信息








